﻿
Imports std

Public Module randomfiletp

    Public Const defaultCacheSize As Int64 = 1024
    Public Const cachesize As String = "cachesize"
    Public Const port As String = "port"
    Public Const allowdir As String = "allowdir"
    Public Const query As String = "q"

    Public WithEvents server As httpServer2 = Nothing
    Public allowdirs As vector(Of String) = Nothing
    Public randomfiles As map(Of String, pointer(Of randomfile)) = Nothing
    Public cache As cacheManager(Of String, pointer(Of bytes)) = Nothing

    Friend pathnormalizer As vector(Of pair(Of String, String)) = Nothing

    Public Sub unhandledException(ByVal ex As Exception)
        server.stop()
        forceQuit(0)
    End Sub

    Friend unhandledExceptionHandler As UnhandledExceptionDelegate = AddressOf unhandledException

    Public Sub main(ByVal iargs() As String)
        registerConsoleExtHandle()
        errorHandle.initial(defaultLogFilename())
        If Not isdebugmode() Then
            registerUnhandledException(unhandledExceptionHandler)
        End If
        destructor.start()

        Dim args As map(Of String, vector(Of String)) = Nothing
        argmgr.parse(iargs, args, True)

        randomfiles = New map(Of String, pointer(Of randomfile))
        Dim icachesize As Int64
        If Not argmgr.getfirstvalue(args, cachesize, icachesize) Then
            icachesize = defaultCacheSize
        End If
        cache = New cacheManager(Of String, pointer(Of bytes))(True, icachesize, 30 * 60 * 1000 * 1000)
        server = New httpServer2()

        If argmgr.containsValue(args, port, True) Then
            Dim ports As vector(Of String) = Nothing
            ports = args(port)
            Dim i As Int64
            For i = 0 To ports.size() - 1
                raiseError("add ports " + ports(i))
                server.addports(ports(i))
            Next
        Else
            raiseError("add port 80 <default>")
            server.addport(80)
        End If

        allowdirs = New vector(Of String)
        If argmgr.containsValue(args, allowdir, True) Then
            allowdirs.push_back(args(allowdir))
        End If

        pathnormalizer = New vector(Of pair(Of String, String))
        pathnormalizer.push_back(make_pair(Of String, String)(character.leftSlash, character.rightSlash))

        raiseError("httpServer2 start.")
        server.start()

        While Not consoleExtSignal() AndAlso server.health()
            sleep(16000)
        End While

        If consoleExtSignal() Then
            raiseError("caught console cancel key event, shutdown.")
        Else
            raiseError("httpServer2 failed, shutdown.", errorHandle.errorType.exclamation)
        End If

        server.stop()

        destructor.stop()
        errorHandle.finalize()
        consoleExtHandleFinish()
    End Sub

    Public Function isAllowdir(ByRef path As String) As Boolean
        assert(Not allowdirs Is Nothing, "allowdirs is nothing before calling isAllowdir(path).")

        Dim i As Int64
        For i = 0 To allowdirs.size() - 1
            If strsame(allowdirs(i), path, strlen(allowdirs(i)), False) Then
                Return True
            End If
        Next

        Return False
    End Function

    Public Function getfile(ByRef s As String, ByRef buff As bytes) As Boolean
        assert(Not cache Is Nothing, "cache is nothing before calling getfile(s,buff).")
        If isemptystring(s) Then
            Return False
        End If

        Dim p As pointer(Of bytes) = Nothing
        If cache.get(s, p) Then
            buff = +p
            Return True
        Else
            Try
                buff = New bytes(IO.File.ReadAllBytes(s))
            Catch
                raiseError("cannot read file " + s, errorHandle.errorType.warning)
                Return False
            End Try
            cache.add(s, New pointer(Of bytes)(buff))

            Return True
        End If
    End Function

    Public Sub processError(ByRef context As httpContext)
        context.setstatuscode(Net.HttpStatusCode.NotFound, "NOTFOUND")
    End Sub

    Public Sub writeContext(ByRef context As httpContext, ByRef buff As bytes, ByRef filename As String)
        If context.setstatuscode(Net.HttpStatusCode.OK, "OK") Then
            context.buff.Write(buff.data, 0, buff.size())
            context.response.ContentEncoding = encoding.utf8
            context.response.ContentType = getdefaultContentType(filename)
        End If
    End Sub

    Public Function processInternalRequest(ByRef context As httpContext) As Boolean
        Dim path As String = Nothing
        path = context.url.path
        If strsame(path, "/ver", False) Then
            If context.setstatuscode(Net.HttpStatusCode.OK, "OK") Then
                context.writer.WriteLine(applicationSign())
                context.response.ContentEncoding = encoding.utf8
                context.response.ContentType = network.http.contentType.texthtmlUtf8
            End If

            Return True
        Else
            Return False
        End If
    End Function

    Private Sub server_receive(ByVal context As httpContext) Handles server.receive
        assert(Not context Is Nothing, "context is nothing when server_receive.")

        If processInternalRequest(context) Then
            Return
        End If

        Dim key As String = Nothing
        key = context.url.query(query)
        kickLeftRightBlank(key)
        If isemptystring(key) Then
            If isdebugmode() Then
                raiseError("get request with empty query, ignore.", errorHandle.errorType.warning)
            End If
            processError(context)
            Return
        End If

        Dim buff As bytes = Nothing
        If isdebugmode() Then
            raiseError("get request from " + context.request.RemoteEndPoint.ToString() + " with query " + key _
                       , errorHandle.errorType.application)
        End If
        Dim it As map(Of String, pointer(Of randomfile)).iterator = Nothing
        strrplc2(key, pathnormalizer, False)

        it = randomfiles.find(key)
        If it = randomfiles.end() Then
            Dim path As String = Nothing
            Dim filter As String
            filter = getfilename(key)
            path = parentpath(key)
            If isAllowdir(path) Then
                Dim randfile As randomfile = Nothing
                randfile = randomfile.create(path, filter, True)
                If Not randfile Is Nothing Then
                    If isdebugmode() Then
                        randfile.refreshMS = 3 * 1000
                    Else
                        randfile.refreshMS = 10 * 60 * 1000
                    End If
                    it = randomfiles.insert(key, New pointer(Of randomfile)(randfile))
                End If
            Else
                processError(context)
            End If
        End If

        If it <> randomfiles.end() Then
            Dim filename As String = Nothing
            filename = (+(+it).second).get()
            If getfile(filename, buff) Then
                writeContext(context, buff, filename)
            Else
                processError(context)
            End If
        Else
            processError(context)
        End If
    End Sub
End Module
